package top.gytf.unifiedcall.producer.support.exception;

import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.util.ArrayUtil;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import top.gytf.unifiedcall.common.util.CodeUtils;
import top.gytf.unifiedcall.model.code.StatusCode;
import top.gytf.unifiedcall.model.exception.BaseException;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.HashSet;
import java.util.Set;
import java.util.function.Function;

import static java.lang.String.format;

/**
 * 基于注解的异常处理适配器
 * <p>用于简化非BaseException子类适配到BaseException的过程
 *
 * @see ExceptionBind
 */
@Slf4j
public class ExceptionBindAdapter implements ExceptionAdapter {
    private final Set<Class<? extends Throwable>> visited = new HashSet<>();
    private final CompositeExceptionAdapter composite = new CompositeExceptionAdapter();

    @Override
    public int order() {
        return Integer.MIN_VALUE;
    }

    @Override
    public int calcSupportScore(Class<? extends Throwable> cls) {
        prepareAdapter(cls);
        return composite.calcSupportScore(cls);
    }

    @Override
    public BaseException convert(Throwable throwable) {
        prepareAdapter(throwable.getClass());
        return composite.convert(throwable);
    }

    /**
     * 准备适配器
     *
     * @param cls 即将要处理的异常类
     */
    private void prepareAdapter(Class<? extends Throwable> cls) {
        if (visited.contains(cls)) {
            return;
        }
        synchronized (visited) {
            if (!visited.add(cls)) {
                return;
            }
            //noinspection unchecked
            ExceptionAdapter adapter = createAdapter((Class<Throwable>) cls);
            if (adapter != null) {
                composite.addAdapter(adapter);
            }
        }
    }

    /**
     * 尝试为该异常类创建适配器
     *
     * @return 如果创建成功，则返回适配器；否则返回{@code null}
     */
    @Nullable
    @SneakyThrows
    private ExceptionAdapter createAdapter(Class<Throwable> cls) {
        ExceptionBind exceptionBind = AnnotationUtils.findAnnotation(cls, ExceptionBind.class);
        if (exceptionBind == null) {
            return null;
        }
        String defaultMessage = CharSequenceUtil.emptyToNull(exceptionBind.defaultMessage());

        Function<Throwable, BaseException> creator;
        if (exceptionBind.exception().length > 0) {
            Class<? extends BaseException> baseException = exceptionBind.exception()[0];
            creator = findCreator(baseException, defaultMessage);
            log.debug("根据exception生成adapter, cls = {}", cls);
        } else if (exceptionBind.location().length > 0) {
            String location = exceptionBind.location()[0];
            StatusCode code = CodeUtils.parseLocation(location);
            Assert.notNull(code, "无法解析location：" + location);
            creator = e -> new BaseException(code, ArrayUtil.firstNonNull(e.getMessage(), defaultMessage), e);
            log.debug("根据location生成adapter, cls = {}", cls);
        } else {
            log.debug("跳过处理空的ExceptionBind, cls = {}", cls);
            return null;
        }

        return new SimpleExceptionAdapter<>(cls, creator);

    }

    private static Function<Throwable, BaseException> findCreator(Class<? extends BaseException> baseException, @Nullable String defaultMessage) throws NoSuchMethodException {
        Constructor<? extends BaseException> constructor;
        Function<Throwable, Object[]> parametersGenerator;
        Function<Throwable, String> messageProvider = e -> ArrayUtil.firstNonNull(e.getMessage(), defaultMessage);
        try {
            // 优先使用（String message, Throwable cause）
            constructor = baseException.getDeclaredConstructor(String.class, Throwable.class);
            parametersGenerator = e -> new Object[]{messageProvider.apply(e), e};
            log.debug("使用(message, cause)构造器生成 [{}] 的creator", baseException);
        } catch (NoSuchMethodException ignored) {
            try {
                // 其次使用（Throwable cause）
                constructor = baseException.getDeclaredConstructor(Throwable.class);
                parametersGenerator = e -> new Object[]{e};
                log.debug("使用(cause)构造器生成 [{}] 的creator", baseException);
            } catch (NoSuchMethodException ignored2) {
                // 最后使用（String message）
                constructor = baseException.getDeclaredConstructor(String.class);
                parametersGenerator = e -> new Object[]{messageProvider.apply(e)};
                log.debug("使用(message)构造器生成 [{}] 的creator", baseException);
            }
        }
        Assert.notNull(constructor, format("无法找到 [%s] 的可用的构造器", baseException));

        Constructor<? extends BaseException> finalConstructor = constructor;
        Function<Throwable, Object[]> finalParametersGenerator = parametersGenerator;
        return e -> {
            try {
                return finalConstructor.newInstance(finalParametersGenerator.apply(e));
            } catch (InstantiationException | IllegalAccessException | InvocationTargetException ex) {
                throw new RuntimeException("构造BaseException失败", ex);
            }
        };
    }
}
